#include "graphicsmanager.h"
#include "baseapplication.h"
#include "texturesdl.h"
#include "textureogl.h"
#include "image.h"
#include "animation.h"
#include "text.h"

#include "sdl_opengl.h"
#include <algorithm>

using std::vector;
using std::list;
using std::string;
using namespace EPOC;

GraphicsManager::GraphicsManager(BaseApplication *pApplication)
{
	m_pApplication = pApplication;
	SetClassID(kCLASSID_GRAPHICSMANAGER);

	m_pDebugText = NULL;
	m_nRenderedObjects = 0;
	m_nFrameCounter = 0;
	m_nRenderCandidates = 0;

	SetRenderSorting(true);

#ifdef _DEBUG
	SetDebugInfo(true);
#else
	SetDebugInfo(false);
#endif
}

GraphicsManager::~GraphicsManager()
{
	GraphicsManager::Cleanup();
}

/*****************************************
  GraphicsManager::Cleanup
 *****************************************/
void GraphicsManager::Cleanup()
{
	m_rRenderContainer.clear();
	m_rAlphaRenderContainer.clear();
	m_rUpdateContainer.clear();

	for(list<GraphicsObject*>::iterator it = m_rImages.begin(); it != m_rImages.end(); it++)
		(*it)->Release();
	m_rImages.clear();

	for(list<GraphicsObject*>::iterator it = m_rAnimations.begin(); it != m_rAnimations.end(); it++)
		(*it)->Release();
	m_rAnimations.clear();

	for(list<GraphicsObject*>::iterator it = m_rTexts.begin(); it != m_rTexts.end(); it++)
		(*it)->Release();
	m_rTexts.clear();

	for(list<Texture*>::iterator it = m_rTextures.begin(); it != m_rTextures.end(); it++)
		(*it)->Release();
	m_rTextures.clear();	
}

/*****************************************
  GraphicsManager::Initialize
 *****************************************/
bool GraphicsManager::Initialize()
{
	if(!m_pApplication)
		return false;

	m_rTextures.clear();

	m_rUpdateContainer.push_back(&m_rImages);
	m_rUpdateContainer.push_back(&m_rAnimations);
	m_rUpdateContainer.push_back(&m_rTexts);

	//if(m_pApplication->GetRenderMode() == kRENDER_MODE_OPENGL)
		InitializeOpenGL();

	m_pDebugText = CreateText(".....", ColorRGB(255, 0, 0));
	m_pDebugText->SetPos(Vector2D(0.0f, 0.0f));
	m_pDebugText->SetZOrder(ZORDER_MAX);
	m_pDebugText->SetCullingEnabled(false);
	//m_pDebugText->SetScl(500.0f, 500.f);
		
	return true;
}

bool GraphicsManager::InitializeOpenGL()
{
	glViewport(0,0,(GLsizei)(m_pApplication->GetWindowWidth()),(GLsizei)(m_pApplication->GetWindowHeight()));						
	glMatrixMode(GL_PROJECTION);											
	glLoadIdentity();														

	
	//gluPerspective(45.0f,(GLfloat)(width)/(GLfloat)(height),1.0f,100.0f);
	glOrtho(0.0, (GLdouble)m_pApplication->GetWindowWidth(), (GLdouble)m_pApplication->GetWindowHeight(), 0.0, -1.0f, 1.0f);
	glMatrixMode(GL_MODELVIEW);												
	glLoadIdentity();												

	SetClearColor( ColorRGB(0, 0, 0));

	glDepthFunc(GL_LEQUAL);

	return true;
}

void GraphicsManager::SetClearColor(ColorRGB rColor)
{
	m_rClearColor = rColor;

	/*if(m_pApplication->GetRenderMode() == kRENDER_MODE_OPENGL)
	{*/
		float fR = ((float)m_rClearColor.m_nRed) / 255.0f;
		float fG = ((float)m_rClearColor.m_nGreen) / 255.0f;
		float fB = ((float)m_rClearColor.m_nBlue) / 255.0f;

		glClearColor(fR, fG, fB, 1.0f);
	//}
}

/*****************************************
  GraphicsManager::SortRenderContainer 
 *****************************************/
void GraphicsManager::SortRenderContainer(vector<GraphicsObject*> *pRenderContainer)
{
	if(pRenderContainer->size() < 2 && IsRenderSortingEnabled() == false)
		return;

	std::sort(pRenderContainer->begin(), pRenderContainer->end(), ZOrderGreater);
}

/*****************************************
  GraphicsManager::SortAlphaRenderContainer 
 *****************************************/
void GraphicsManager::SortAlphaRenderContainer(vector<GraphicsObject*> *pRenderContainer)
{
	if(pRenderContainer->size() < 2 && IsRenderSortingEnabled() == false)
		return;

	std::sort(pRenderContainer->begin(), pRenderContainer->end(), ZOrderGreater);
}


/*****************************************
  GraphicsManager::Update 
 *****************************************/
bool GraphicsManager::Update(float fTimeDelta)
{
	m_nFrameCounter++;

	// clear render container
	m_rRenderContainer.clear();
	m_rAlphaRenderContainer.clear();
	m_nRenderCandidates = 0;

	// update all objects
	for(list< list<GraphicsObject *> *>::iterator contIt = m_rUpdateContainer.begin(); contIt != m_rUpdateContainer.end(); contIt++)
	{
		m_nRenderCandidates += (int)(*contIt)->size();
		for(list<GraphicsObject* >::iterator it = (*contIt)->begin(); it != (*contIt)->end();)
		{
			if((*it)->Update(fTimeDelta) == false)
			{
				GraphicsObject *pObject = (*it);
				it = (*contIt)->erase(it);
				delete pObject;
			}
			else
			{
				// add this object to be rendered if it passes the tests
				if((*it)->IsVisible() && (*it)->IsVisibleWindow(m_pApplication->GetWindowWidth(), m_pApplication->GetWindowHeight()))
				{
					/*if(m_pApplication->GetRenderMode() == kRENDER_MODE_SDL)
					{
						m_rRenderContainer.push_back((*it));
					}
					else if(m_pApplication->GetRenderMode() == kRENDER_MODE_OPENGL)
					{*/
						if( (*it)->IsAlphaEnabled() )
							m_rAlphaRenderContainer.push_back((*it));
						else	
							m_rRenderContainer.push_back((*it));
					//}
				}
				
				it++;
			}
		}
	}

	/*if(m_pApplication->GetRenderMode() == kRENDER_MODE_SDL)
		SortRenderContainer(&m_rRenderContainer);
	else if(m_pApplication->GetRenderMode() == kRENDER_MODE_OPENGL)
	{*/
		SortRenderContainer(&m_rRenderContainer);
		SortAlphaRenderContainer(&m_rAlphaRenderContainer);
	//}
	
	if(UpdateDebugInformation(fTimeDelta) == false) 
		return false;

	return true;
}


bool GraphicsManager::UpdateDebugInformation(float fTimeDelta)
{
	UNREFERENCED_PARAMETER(fTimeDelta);

	if(m_pApplication && m_pDebugText && IsDebugInfoEnabled())
	{
		// Ouputput debug info
		string sText;
		char buffer[512];
			
#ifdef _DEBUG
		/*if(m_pApplication->GetRenderMode() == kRENDER_MODE_OPENGL)*/
			sprintf_s(&buffer[0], 512, "DEBUG, OpenGL, FPS: %i, TimeDelta: %f, Render candidates: %i, Actual rendered objects: %i", m_pApplication->GetFPS(), m_pApplication->GetTimeDelta(), m_nRenderCandidates, m_nRenderedObjects);
		/*else
			sprintf_s(&buffer[0], 512, "DEBUG, FPS: %i, TimeDelta: %f, Render candidates: %i, Actual rendered objects: %i", m_pApplication->GetFPS(), m_pApplication->GetTimeDelta(), m_nRenderCandidates, m_nRenderedObjects);*/
#else
		if(m_pApplication->GetRenderMode() == kRENDER_MODE_OPENGL)
			sprintf_s(&buffer[0], 512, "RELEASE, OpenGL, FPS: %i, TimeDelta: %f, Render candidates: %i, Rendered objects: %i", m_pApplication->GetFPS(), m_pApplication->GetTimeDelta(), m_nRenderCandidates, m_nRenderedObjects);
		else
			sprintf_s(&buffer[0], 512, "RELEASE, FPS: %i, TimeDelta: %f, Render candidates: %i, Rendered objects: %i", m_pApplication->GetFPS(), m_pApplication->GetTimeDelta(), m_nRenderCandidates, m_nRenderedObjects);
#endif
		sText = buffer;
		m_pDebugText->SetText(sText);
	}
	else if(m_pDebugText && !IsDebugInfoEnabled())
		m_pDebugText->SetVisible(false);
	

	return true;
}

/*****************************************
  GraphicsManager::Render 
 *****************************************/
void GraphicsManager::Render(SDL_Renderer* pRenderer)
{
	if(!pRenderer)
		return;

	m_nRenderedObjects = 0;

	/*switch(m_pApplication->GetRenderMode())
	{
		case kRENDER_MODE_SDL:
			RenderSDL(pDestSurface);
		break;

		case kRENDER_MODE_OPENGL:*/
			RenderOGL(pRenderer);
	/*	break;
	}*/
}


/*****************************************
  GraphicsManager::RenderSDL 
 *****************************************/
void GraphicsManager::RenderSDL(TextureSDL* pDestSurface)
{
	SDL_FillRect( pDestSurface->GetSDLSurface(), 0, SDL_MapRGB( pDestSurface->GetSDLSurface()->format, m_rClearColor.m_nRed, m_rClearColor.m_nGreen, m_rClearColor.m_nBlue ) );
	 
	// Lock surface if needed
	if ( SDL_MUSTLOCK( pDestSurface->GetSDLSurface() ) )
		if ( SDL_LockSurface( pDestSurface->GetSDLSurface() ) < 0 )
			return;

	
	for(vector<GraphicsObject*>::iterator it = m_rRenderContainer.begin(); it != m_rRenderContainer.end(); it++)
	{
		//(*it)->Render(pDestSurface);
		m_nRenderedObjects++;
	}


	// Unlock if needed
	if ( SDL_MUSTLOCK( pDestSurface->GetSDLSurface() ) ) 
		SDL_UnlockSurface( pDestSurface->GetSDLSurface() );
	 
	// Tell SDL to update the whole gScreen
	//SDL_Flip( pDestSurface->GetSDLSurface() );

}

/*****************************************
  GraphicsManager::RenderOGL
 *****************************************/
void GraphicsManager::RenderOGL(SDL_Renderer* pRenderer)
{	
	SDL_SetRenderDrawColor(pRenderer, 0, 0, 0, 255);
	SDL_RenderClear(pRenderer);
	//glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glLoadIdentity();

	for(vector<GraphicsObject*>::iterator it = m_rRenderContainer.begin(); it != m_rRenderContainer.end(); it++)
	{
		(*it)->Render();
		m_nRenderedObjects++;
	}

	for(vector<GraphicsObject*>::iterator it = m_rAlphaRenderContainer.begin(); it != m_rAlphaRenderContainer.end(); it++)
	{
		(*it)->Render();
		m_nRenderedObjects++;
	}
	 
	//SDL_GL_SwapBuffers();


	SDL_RenderPresent(pRenderer);

	glFlush();
}


/*****************************************
  GraphicsManager::LoadTextureFromFile 
 *****************************************/
Texture* GraphicsManager::LoadTextureFromFile(string sFilePath, bool bExclusive, bool bEditable) 
{
	Texture *pTexture = NULL;

	if(bExclusive == false)
		pTexture = GetTexture(sFilePath);

	if(pTexture == NULL)
	{
		SDL_Surface *pImageSurface = IMG_Load(sFilePath.c_str());
		if(!pImageSurface)
		{
			printf("GraphicsManager: Error Couldn't load image, %s\n", IMG_GetError());
			// handle error
		}
		else
		{
			/*SDL_Surface *pSurface = SDL_DisplayFormatAlpha(pImageSurface);
			SDL_FreeSurface(pImageSurface);
			pImageSurface = pSurface;*/

		/*	switch( m_pApplication->GetRenderMode() )
			{
				case kRENDER_MODE_SDL:
				{
					pTexture = new TextureSDL(this);
					if( ((TextureSDL*)pTexture)->Initialize(sFilePath, sFilePath, pImageSurface, bExclusive) == false)
					{
						pTexture->Release();
						pTexture = NULL;
					}
				}
				break;

				case kRENDER_MODE_OPENGL:
				{*/
					pTexture = new TextureOGL(this);
					if( ((TextureOGL*)pTexture)->Initialize(sFilePath, sFilePath, pImageSurface, bExclusive, bEditable) == false)
					{
						pTexture->Release();
						pTexture = NULL;
					}
				/*}
				break;
			}*/

			if(pTexture)
				AddTexture(pTexture);
		}
	}
	else
		pTexture->AddRef();
	
	return pTexture;
}


/*****************************************
  GraphicsManager::CreateTexture 
 *****************************************/
Texture* GraphicsManager::CreateTexture(string sName, bool bExclusive, bool bEditable)
{
	Texture *pTexture = NULL;
	/*switch( m_pApplication->GetRenderMode() )
	{
		case kRENDER_MODE_SDL:
		{
			TextureSDL *pTextureSDL = new TextureSDL(this);
			if( pTextureSDL->Initialize(sName, "", NULL, bExclusive) == false)
			{
				pTextureSDL->Release();
				pTextureSDL = NULL;
			}

			pTexture = pTextureSDL;
		}
		break;

		case kRENDER_MODE_OPENGL:
		{*/
			TextureOGL *pTextureOGL = new TextureOGL(this);
			if( pTextureOGL->Initialize(sName, "", NULL, bExclusive, bEditable) == false)
			{
				pTextureOGL->Release();
				pTextureOGL = NULL;
			}

			pTexture = pTextureOGL;
		/*}
		break;
	}*/

	if(pTexture)
		AddTexture(pTexture);

	return pTexture;
}


/*****************************************
  GraphicsManager::GetTexture 
 *****************************************/
Texture* GraphicsManager::GetTexture(string sName)
{
	Texture *pTexture = NULL;

	if(!m_rTextures.empty())
	{
		for(list<Texture*>::iterator it = m_rTextures.begin(); it != m_rTextures.end(); it++)
		{
			if((*it)->GetName().compare(sName) == 0)
			{
				if((*it)->IsExclusive() == false)
				{
					pTexture = (*it);
					break;
				}
			}
		}
	}

	return pTexture;
}

/*****************************************
  GraphicsManager::GetTexture 
 *****************************************/
void GraphicsManager::AddTexture(Texture *pTexture)
{
	m_rTextures.push_back(pTexture);
}

/*****************************************
  GraphicsManager::DestroyTexture 
 *****************************************/
bool GraphicsManager::DestroyTexture(Texture *pTexture)
{
	if(RemoveTexture(pTexture) )
	{
		pTexture->Release();
		pTexture = NULL;
		return true;
	}
	else
		pTexture->Release();
	
	return false;
}


/*****************************************
  GraphicsManager::RemoveTexture 
 *****************************************/
bool GraphicsManager::RemoveTexture(Texture *pTexture)
{
	//if(!m_rTextures.empty())
	//	m_rTextures.remove(pTexture);

	if(pTexture->GetRefCount() < 2 && !m_rTextures.empty())
	{
		m_rTextures.remove(pTexture);
		return true;
	}
	
	return false;
}


/*****************************************
  GraphicsManager::CreateImage 
 *****************************************/
Image* GraphicsManager::CreateImage(string sFilePath, bool bExclusive, bool bEditable)
{
	Texture *pTexture = LoadTextureFromFile(sFilePath, bExclusive, bEditable);
	if(!pTexture)
		return false;

	Image *pImage = new Image();
	if(pImage->Initialize(pTexture/*, m_pApplication->GetRenderMode()*/) == false)
	{
		pImage->Release();
		pImage = NULL;
	}
	else	
		AddImageObject(pImage);

	return pImage;
}

/*****************************************
  GraphicsManager::CreateImage 
 *****************************************/
Image* GraphicsManager::CreateImage(Texture *pTexture)
{
	Image *pImage = NULL;

	if(	pTexture 
		/*&&
		( (pTexture->IsClass(kCLASSID_TEXTURE_SDL) && m_pApplication->GetRenderMode() == kRENDER_MODE_OPENGL)
		||	
		(pTexture->IsClass(kCLASSID_TEXTURE_OGL) && m_pApplication->GetRenderMode() == kRENDER_MODE_SDL)) */)
	{
		return pImage;
	}

	pImage = new Image();
	if(pImage->Initialize(pTexture/*, m_pApplication->GetRenderMode()*/) == false)
	{
		pImage->Release();
		pImage = NULL;
	}
	else	
		AddImageObject(pImage);

	return pImage;

}


/*****************************************
  GraphicsManager::DestroyImage 
 *****************************************/
void GraphicsManager::DestroyImage(Image *pImage)
{
	pImage->Release();
	RemoveImageObject(pImage);
}


/*****************************************
  GraphicsManager::CreateAnimation 
 *****************************************/
Animation* GraphicsManager::CreateAnimation(string sFilePath, bool bExclusive, bool bEditable)
{
	Texture *pTexture = LoadTextureFromFile(sFilePath, bExclusive, bEditable);
	if(!pTexture)
		return false;

	Animation *pAnimation = new Animation();
	if(pAnimation->Initialize(pTexture/*, m_pApplication->GetRenderMode()*/) == false)
	{
		delete pAnimation;
		pAnimation = NULL;
	}
	else
		AddAnimationObject(pAnimation);

	return pAnimation;
}

/*****************************************
  GraphicsManager::CreateAnimation 
 *****************************************/
Animation* GraphicsManager::CreateAnimation(Texture *pTexture)
{
	Animation *pAnimation = NULL;

	if(	pTexture 
		/*&&
		( (pTexture->IsClass(kCLASSID_TEXTURE_SDL) && m_pApplication->GetRenderMode() == kRENDER_MODE_OPENGL)
		||	
		(pTexture->IsClass(kCLASSID_TEXTURE_OGL) && m_pApplication->GetRenderMode() == kRENDER_MODE_SDL)) */)
	{
		return pAnimation;
	}

	pAnimation = new Animation();
	if(pAnimation->Initialize(pTexture/*, m_pApplication->GetRenderMode()*/) == false)
	{
		delete pAnimation;
		pAnimation = NULL;
	}
	else
		AddAnimationObject(pAnimation);

	return pAnimation;
}


/*****************************************
  GraphicsManager::DestroyAnimation 
 *****************************************/
void GraphicsManager::DestroyAnimation(Animation *pAnimation)
{
	pAnimation->Release();
	RemoveAnimationObject(pAnimation);
}


/*****************************************
  GraphicsManager::CreateText 
 *****************************************/
Text* GraphicsManager::CreateText(string sText, ColorRGB rTextColor, int nFontSize, string sFontPath)
{
	Text *pText = new Text(this);
	if(pText->Initialize(NULL/*, m_pApplication->GetRenderMode()*/) == false || pText->Load(sFontPath, nFontSize) == false)
	{
		delete pText;
		pText = NULL;
	}
	else
	{	
		pText->SetText(sText);
		pText->SetTextColor(rTextColor);
		
		AddTextObject(pText);
	}

	return pText;
}


/*****************************************
  GraphicsManager::DestroyText 
 *****************************************/
void GraphicsManager::DestroyText(Text *pText)
{
	pText->Release();
	RemoveTextObject(pText);
}


/*****************************************
  GraphicsManager::AddRenderObject 
 *****************************************/
void GraphicsManager::AddRenderObject(GraphicsObject *pObject)
{
	m_rRenderContainer.push_back(pObject);
}


/*****************************************
  GraphicsManager::AddImageObject 
 *****************************************/
void GraphicsManager::AddImageObject(class Image* pObject)
{
	m_rImages.push_back(pObject);
}


/*****************************************
  GraphicsManager::RemoveImageObject 
 *****************************************/
void GraphicsManager::RemoveImageObject(Image* pObject)
{
	if(pObject->GetRefCount() < 2 && !m_rImages.empty())
		m_rImages.remove(pObject);
}


/*****************************************
  GraphicsManager::AddAnimationObject 
 *****************************************/
void GraphicsManager::AddAnimationObject(Animation* pObject)
{
	m_rAnimations.push_back(pObject);
}


/*****************************************
  GraphicsManager::RemoveAnimationObject 
 *****************************************/
void GraphicsManager::RemoveAnimationObject(Animation* pObject)
{
	if(pObject->GetRefCount() < 2 && !m_rAnimations.empty())
		m_rAnimations.remove(pObject);
}


/*****************************************
  GraphicsManager::AddTextObject 
 *****************************************/
void GraphicsManager::AddTextObject(Text* pObject)
{
	m_rTexts.push_back(pObject);
}


/*****************************************
  GraphicsManager::RemoveTextObject 
 *****************************************/
void GraphicsManager::RemoveTextObject(Text* pObject)
{
	if(pObject->GetRefCount() < 2 && !m_rTexts.empty())
		m_rTexts.remove(pObject);
}


/*****************************************
  GraphicsManager::ZOrderGreater 
 *****************************************/
int GraphicsManager::ZOrderGreater(GraphicsObject *pObject1, GraphicsObject *pObject2)
{ 
	return pObject1->GetZOrder() < pObject2->GetZOrder();
}

/*****************************************
  GraphicsManager::ZOrderSmaller 
 *****************************************/
int GraphicsManager::ZOrderSmaller(GraphicsObject *pObject1, GraphicsObject *pObject2)
{ 
	return pObject1->GetZOrder() > pObject2->GetZOrder();
}

/*****************************************
  GraphicsManager::SetRenderSorting 
 *****************************************/
void GraphicsManager::SetRenderSorting(bool bStatus)
{
	m_bSortRenderContainer = bStatus;

	//switch(m_pApplication->GetRenderMode())
	//{
	//	case kRENDER_MODE_SDL:
	//	break;

	//	case kRENDER_MODE_OPENGL:
	//	{
			if(m_bSortRenderContainer)
				glEnable(GL_DEPTH_TEST);
			else
				glDisable(GL_DEPTH_TEST);
		/*}
		break;

	}*/
}